﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Net;
using CamShared;
using System.Diagnostics;

namespace UPnP
{
    public class UPnPDiscovery
    {
        public static UPnPDiscovery SharedHost = new UPnPDiscovery();


        public object DiscoveryLock = new object();
        public delegate void DeviceDiscovery(Device d);
        public event DeviceDiscovery OnDeviceDiscovered;
        public List<Device> DiscoveredDevices = new List<Device>();

        public bool DiscoveryRequested = false;

        private Socket sock = null;

        IPEndPoint ipe = new IPEndPoint(IPAddress.Broadcast, 1900);

        private UPnPDiscovery()
        {
            sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
            Thread t = new Thread(SocketMon);
            t.Start();
        }

        internal void Broadcast(string msg)
        {

            byte[] data = Encoding.ASCII.GetBytes(msg);

            sock.Ttl = 2;
            sock.SendTo(data, ipe);
        }

        public void Discover(SSDP req)
        {
            Trace.WriteLine("SSDP Discovery");
            DiscoveryRequested = true;
            //Broadcast(req.GetRequest());
            new Thread(new ParameterizedThreadStart(BroadcastThread)).Start(req.GetRequest());
        }

        private void BroadcastThread(object o)
        {
            string req = (string)o;
            if (!string.IsNullOrEmpty(req))
            {
                while (true)
                {
                    Trace.WriteLine("Checking UPnP...");
                    Broadcast(req);
                    Thread.Sleep(1000 * 60 * 5);
                }
            }
        }


        private void OnResponse(string req, IPEndPoint from)
        {
            try
            {
                Message msg = new Message(req);

                if (msg.RequestLine == "HTTP/1.1 200 OK")
                {
                    // response from search
                }
                else if (msg.RequestLine == "NOTIFY * HTTP/1.1")
                {
                    // notification of ssdp:alive
                }

                try
                {
                    Device d = null;
                    try
                    {
                        string xml = msg.GetXml();
                        d = new Device(xml);

                    }
                    catch (Exception)
                    {
                        d = new Device();
                    }
                    d.USN = msg.UniqueServiceIdentifier;
                    d.Location = msg.Location;
                    d.DeviceName = msg.Header("DeviceName.davux.com");
                    d.URL = msg.Header("URL.davux.com");
                    Trace.WriteLine(from + " Discovered UPnP Device: " + d.USN);
                    lock (DiscoveryLock)
                    {
                        if (OnDeviceDiscovered != null)
                        {
                            OnDeviceDiscovered.Invoke(d);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Trace.WriteLine("UPnP Error getting xml: " + ex);
                }
                // also handle NOTIFY and M-SEARCH
            }
            catch (Exception ex)
            {
                Trace.WriteLine("Error in OnResponse: " + ex);
            }
        }

        private void SocketMon()
        {
            Thread.CurrentThread.Name = "UPNP Discovery";
            try
            {
                int length = 0;
                sock.Blocking = true;
                do
                {

                    byte[] buffer = new byte[sock.ReceiveBufferSize];
                    while (sock.Available == 0)
                    {
                        Thread.Sleep(100);
                    }

                    //length = sock.Receive(buffer,buffer.Length, SocketFlags.None);

                    EndPoint ep = new IPEndPoint(IPAddress.Any, 0);
                    length = sock.ReceiveFrom(buffer, ref ep);

                    IPAddress[] ips = Dns.GetHostEntry(Dns.GetHostName()).AddressList;

                    foreach (IPAddress ip in ips)
                    {

                        if (ip.ToString() == ((IPEndPoint)ep).Address.ToString())
                        {
                            //Trace.WriteLine("Compare: " + ip + " " + ((IPEndPoint)ep).Address);
                            //Trace.WriteLine("Ignor UPnP Response from localhost");
                            length = 0;
                        }
                    }

                    

                    if (length > 0 && buffer.Length > 0)
                    {
                        try
                        {
                            OnResponse(Encoding.ASCII.GetString(buffer, 0, length), (IPEndPoint) ep);
                        }
                        catch (Exception ex)
                        {
                            Trace.WriteLine("Error UPnP OnResponse: " + ex);
                        }
                    }
                } while (true);
            }
            catch (SocketException aex)
            {
                Trace.WriteLine("UPnP Discovery SE: " + aex);
                Thread.Sleep(1000);
                SocketMon();
            }
            catch (Exception ex)
            {
                Trace.WriteLine("UPnP Discovery: " + ex);
            }
        }
    }
}
